import Foundation
import Tauri
import UIKit
import WebKit

class LibsqlPlugin: Plugin {
    
    // Basic LibSQL operations
    @objc public func connect(_ invoke: Invoke) throws {
        let options = try invoke.parseArgs(ConnectOptions.self)
        // TODO: Implement using LibSQL Swift SDK
        // let connection = try LibSQL.connect(localPath: options.localPath, url: options.url, authToken: options.authToken)
        invoke.resolve(["connectionId": "ios-connection-id"])
    }
    
    @objc public func execute(_ invoke: Invoke) throws {
        let options = try invoke.parseArgs(ExecuteOptions.self)
        // TODO: Implement SQL execution with LibSQL Swift SDK
        // let result = try connection.execute(sql: options.sql, params: options.params)
        invoke.resolve(["rowsAffected": 1])
    }
    
    @objc public func query(_ invoke: Invoke) throws {
        let options = try invoke.parseArgs(QueryOptions.self)
        // TODO: Implement SQL query with LibSQL Swift SDK
        // let result = try connection.query(sql: options.sql, params: options.params)
        invoke.resolve([
            "columns": [],
            "rows": []
        ])
    }
    
    @objc public func sync(_ invoke: Invoke) throws {
        let options = try invoke.parseArgs(SyncOptions.self)
        // TODO: Implement sync with LibSQL Swift SDK
        // try connection.sync()
        invoke.resolve()
    }
    
    @objc public func close(_ invoke: Invoke) throws {
        let options = try invoke.parseArgs(CloseOptions.self)
        // TODO: Implement connection close with LibSQL Swift SDK
        // connection.close()
        invoke.resolve()
    }
    
    // Vector operations
    @objc public func vector_top_k(_ invoke: Invoke) throws {
        let options = try invoke.parseArgs(VectorTopKOptions.self)
        // TODO: Implement vector_top_k using LibSQL Swift SDK
        // Example implementation:
        // let sql = "SELECT * FROM vector_top_k('\(options.indexName)', vector32('\(options.queryVector)'), \(options.k))"
        // let result = try connection.query(sql: sql)
        invoke.resolve([
            "columns": ["id"],
            "rows": []
        ])
    }
    
    @objc public func vector_distance_cos(_ invoke: Invoke) throws {
        let request = try invoke.parseArgs(VectorDistanceRequest.self)
        // TODO: Implement cosine distance calculation
        // Example implementation:
        // let vector1Sql = convertVectorToSql(request.vector1)
        // let vector2Sql = convertVectorToSql(request.vector2)
        // let sql = "SELECT vector_distance_cos(\(vector1Sql), \(vector2Sql))"
        // let result = try connection.query(sql: sql)
        invoke.resolve(0.5)
    }
    
    @objc public func vector_distance_l2(_ invoke: Invoke) throws {
        let request = try invoke.parseArgs(VectorDistanceRequest.self)
        // TODO: Implement L2 distance calculation
        // Similar to cosine distance but using vector_distance_l2
        invoke.resolve(1.0)
    }
    
    @objc public func vector_extract(_ invoke: Invoke) throws {
        let request = try invoke.parseArgs(VectorExtractRequest.self)
        // TODO: Implement vector extraction from binary format
        // let sql = "SELECT vector_extract(?)"
        // let result = try connection.query(sql: sql, params: [request.vectorBlob])
        invoke.resolve([0.1, 0.2, 0.3, 0.4])
    }
    
    @objc public func create_vector_index(_ invoke: Invoke) throws {
        let request = try invoke.parseArgs(CreateVectorIndexRequest.self)
        // TODO: Implement vector index creation
        // let settingsStr = request.settings?.joined(separator: "', '") ?? ""
        // let sql = "CREATE INDEX \(request.indexName) ON \(request.tableName)(libsql_vector_idx(\(request.columnName)\(settingsStr.isEmpty ? "" : ", '\(settingsStr)'")))"
        // let rowsAffected = try connection.execute(sql: sql)
        invoke.resolve([
            "rowsAffected": 1,
            "lastInsertRowid": NSNull()
        ])
    }
    
    // Data structures for request parsing
    struct ConnectOptions: Codable {
        let localPath: String
        let url: String?
        let authToken: String?
    }
    
    struct ExecuteOptions: Codable {
        let connectionId: String
        let sql: String
        let params: [Value]?
        let namedParams: [String: Value]?
    }
    
    struct QueryOptions: Codable {
        let connectionId: String
        let sql: String
        let params: [Value]?
        let namedParams: [String: Value]?
    }
    
    struct SyncOptions: Codable {
        let connectionId: String
    }
    
    struct CloseOptions: Codable {
        let connectionId: String
    }
    
    struct VectorTopKOptions: Codable {
        let connectionId: String
        let indexName: String
        let queryVector: [Float]
        let k: Int
        let vectorType: String?
    }
    
    struct VectorDistanceRequest: Codable {
        let connectionId: String
        let vector1: Value
        let vector2: Value
    }
    
    struct VectorExtractRequest: Codable {
        let connectionId: String
        let vectorBlob: Data
    }
    
    struct CreateVectorIndexRequest: Codable {
        let connectionId: String
        let indexName: String
        let tableName: String
        let columnName: String
        let settings: [String]?
    }
    
    enum Value: Codable {
        case null
        case integer(Int64)
        case real(Double)
        case text(String)
        case blob(Data)
        case vector(data: [Float], vectorType: String)
        
        enum CodingKeys: String, CodingKey {
            case type, value
        }
        
        enum ValueType: String, Codable {
            case null = "Null"
            case integer = "Integer"
            case real = "Real"
            case text = "Text"
            case blob = "Blob"
            case vector = "Vector"
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let type = try container.decode(ValueType.self, forKey: .type)
            
            switch type {
            case .null:
                self = .null
            case .integer:
                let value = try container.decode(Int64.self, forKey: .value)
                self = .integer(value)
            case .real:
                let value = try container.decode(Double.self, forKey: .value)
                self = .real(value)
            case .text:
                let value = try container.decode(String.self, forKey: .value)
                self = .text(value)
            case .blob:
                let value = try container.decode(Data.self, forKey: .value)
                self = .blob(value)
            case .vector:
                let vectorContainer = try container.nestedContainer(keyedBy: VectorKeys.self, forKey: .value)
                let data = try vectorContainer.decode([Float].self, forKey: .data)
                let vectorType = try vectorContainer.decode(String.self, forKey: .vectorType)
                self = .vector(data: data, vectorType: vectorType)
            }
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            
            switch self {
            case .null:
                try container.encode(ValueType.null, forKey: .type)
                try container.encodeNil(forKey: .value)
            case .integer(let value):
                try container.encode(ValueType.integer, forKey: .type)
                try container.encode(value, forKey: .value)
            case .real(let value):
                try container.encode(ValueType.real, forKey: .type)
                try container.encode(value, forKey: .value)
            case .text(let value):
                try container.encode(ValueType.text, forKey: .type)
                try container.encode(value, forKey: .value)
            case .blob(let value):
                try container.encode(ValueType.blob, forKey: .type)
                try container.encode(value, forKey: .value)
            case .vector(let data, let vectorType):
                try container.encode(ValueType.vector, forKey: .type)
                var vectorContainer = container.nestedContainer(keyedBy: VectorKeys.self, forKey: .value)
                try vectorContainer.encode(data, forKey: .data)
                try vectorContainer.encode(vectorType, forKey: .vectorType)
            }
        }
        
        enum VectorKeys: String, CodingKey {
            case data, vectorType
        }
    }
    
    // Helper function to convert Value to LibSQL parameter format
    private func convertVectorToSql(_ value: Value) -> String {
        switch value {
        case .vector(let data, let vectorType):
            let functionName: String
            switch vectorType {
            case "Float64":
                functionName = "vector64"
            case "Float32":
                functionName = "vector32"
            case "Float16":
                functionName = "vector16"
            case "FloatB16":
                functionName = "vectorb16"
            case "Float8":
                functionName = "vector8"
            case "Float1Bit":
                functionName = "vector1bit"
            default:
                functionName = "vector32"
            }
            let dataStr = data.map { String($0) }.joined(separator: ",")
            return "\(functionName)('[\(dataStr)]')"
        default:
            return "NULL"
        }
    }
} 